#include <math.h>
 

#include <stdio.h>
#include <assert.h>
//#include <GL/gl.h>
#include <iostream>
#include <GL/glew.h>
#include <GL/glut.h>

#include "RayCaster.h"
#include "TransferFunction.h"

#pragma warning(disable: 4251 4275) //get rid of warning in glui
#include <GL/glui.h>

#pragma comment (lib, "glew32.lib")


#include  "..\..\RenderingEngine\engineCore\testProject\trackBallInteraction.h"
#include "..\..\RenderingEngine\engineCore\common\Math\ArcBall.h"

const char *TF_string_list[] = { "SolidStyle1", "SolidStyle2","TransparentSytle1", "TransparentSytle2" };
int   curr_string = 0;

extern "C" void unitTest();



#ifdef VISUS
#include <visuscpp/visus_cpp.h>
#include <visuscpp/core/visus_memoryblock.h>
#include <include/visus.h>
using namespace VisusCpp;
//visus////////////////////////////////////////////////
#pragma comment(lib, "libvisusio.lib")
#pragma comment(lib, "libvisuscpp.lib")
#pragma comment(lib, "Juce.lib")
#pragma comment(lib, "FreeImageLib.lib")
//juce windows
#pragma comment(lib, "Vfw32.lib")
#pragma comment(lib, "Version.lib")
#pragma comment(lib, "wininet.lib")
#endif

using namespace std;
int w=800;
int h=800;
//=====App===status================
GLUI *glui;
int window_id;
//lighting
float light_rotate[16] ={1, 0, 0, 0, 
								 0, 1, 0, 0, 
								 0, 0, 1, 0,
								 0, 0, 0, 1};


float stepsize = 0.0136;
float var = 0.01;
float integralMethod = 1;
float isovalue = 0.2;
int showPacking = 1;
int viewMethod = 0;
int showTF = 0;

//=================================
RayCaster *raycaster;
TransferFunction *transferfunction;
ArcBall *pArcBall;

void display()                              
{
	raycaster->setRotationMatrix(pArcBall->GetRotation().array);
	raycaster->setTranslation(pArcBall->GetTranslationVec());   //get the pointer to the first element int the vector


	raycaster->setStepSize(stepsize);
	raycaster->setIsoValue((float)showPacking);
	raycaster->setVar(var);

	//raycaster->Render();
	if(showPacking==1)
	{
		memcpy(raycaster->light_rotate, light_rotate, sizeof(float)*16);
		raycaster->raycastprog.BeginProgram();
		raycaster->screenRaycastprog.setUniform1f("integralMethod", integralMethod);
		raycaster->raycastprog.setUniform1f("insideView", float(viewMethod));
		raycaster->Render();
	}
	else
	{
		raycaster->screenRaycastprog.BeginProgram();
		raycaster->screenRaycastprog.setUniform1f("integralMethod", integralMethod);
		raycaster->screenRaycastprog.setUniform1f("insideView", float(viewMethod));
		raycaster->RenderScreenAlign();
	}

	if(showTF == 1)
		transferfunction->Render();

	glutSwapBuffers();
}

void idle(void)                                 
{
	glutSetWindow(window_id);
	glutPostRedisplay();
}

void reshape (int w, int h)
{
	raycaster->resize(w,h);
	pArcBall->SetWindowSize(w,h);
}

void keyboard(unsigned char key, int x, int y)  { }
void mouse(int button, int state, int x, int y) 
{
	if(showTF == 0)	 //if not adjusting the TF
	{
  		if(button == GLUT_LEFT_BUTTON )
		{
			if(state == GLUT_DOWN)
				pArcBall->OnLeftButtonDown(x,y);
			else
				pArcBall->OnLeftButtonUp(x,y);
		}
		else if(button == GLUT_RIGHT_BUTTON)
		{
			if(state == GLUT_DOWN)
				pArcBall->OnRightButtonDown(x, y);
			else
				pArcBall->OnRightButtonUp(x, y);
		}
		else if(button == GLUT_MIDDLE_BUTTON)
		{
			if(state == GLUT_DOWN)
				pArcBall->OnMiddleButtonDown(x, y);
			else
				pArcBall->OnMiddleButtonUp(x, y);
		}
	}


	transferfunction->MouseButton(button, state, x, h-y);
}
void move(int x, int y)                         
{
	transferfunction->MouseMove(x,h-y);

	pArcBall->OnMouseMove(x,y);


  glutPostRedisplay(); 

}
void init_gl()
{
    glewInit();
    if (!glewIsSupported("GL_VERSION_2_0 GL_EXT_framebuffer_object")) {
        printf("Required extensions not supported.\n");
        exit(-1);
		if (!GLEW_EXT_texture3D)
			exit(-1);
    }
	char* version = (char*)glGetString(GL_VERSION);

	//glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

	raycaster->InitializeRayCaster();

	glClearColor(0.2, 0.2, 0.2, 1.0);
	//raycaster->LoadVolume("packtest258X258X258-NN.raw", 0);
	//raycaster->LoadVolume("packtest258X258X258-Linear.raw", 1);

	//raycaster->loadBlockPool("headBlockPool-128.raw",384,384,384);
	//raycaster->loadNodePool2D("headNodePool2D-128.raw",11,9);

	//raycaster->loadBlockPool("headBlockPool-128-boundary.raw",387,387,387);
	//raycaster->loadNodePool2D("headNodePool2D-128.raw",11,9);
	//raycaster->loadBlockPool("headBlockPacking-64-boundary-zyx-260X260X260.raw",260,260,260);
	//raycaster->loadNodePool3D("blockPool3D-64-ddd-6.raw",6,6,6);	//intensity alpha

	//raycaster->loadBlockPool("headBlockPacking-32-boundary-zyx-264X264X264.raw",264,264,264);
	//raycaster->loadNodePool3D("headnodePool3D-32-ddd-10.raw",10,10,10);	//intensity alpha

	//raycaster->loadBlockPool("headBlockPacking-32-boundary-zyx-198X198X198.raw",198,198,198);
	//raycaster->loadNodePool3D("headnodePool3D-32-ddd-8.raw",8,8,8);	//intensity alpha

	//raycaster->loadBlockPool("headBlockPacking-128-boundary-zyx-258X258X258.raw",258,258,258);
	//raycaster->loadNodePool3D("headnodePool3D-128-ddd-2.raw",2,2,2);	//intensity alpha

	//raycaster->loadNodePool3D("stagbeetleblockPool3D-32-ddd-14.raw",14,14,14);	//intensity alpha
	//raycaster->loadBlockPool("stagbeetleBlockPacking-32-boundary-zyx-363X363X363.raw",363,363,363);

	if(showPacking!=1)
	{
		raycaster->loadNodePool3D("../../Data/stagbeetleblockPool3D-32-ddd-14.raw",14,14,14);	//intensity alpha
		raycaster->loadBlockPool("../../Data/stagbeetleBlockPacking-32-boundary-zyx-363X363X363.raw",363,363,363);
		//raycaster->loadNodePool3D("1GnodePool3D-32-ddd-28.raw",28,28,28);	//intensity alpha
 	//	raycaster->loadBlockPool("1GBlockPacking-32-boundary-zyx-825X825X825.raw",825,825,825);
	}
	else
	{
		raycaster->loadNodePool3D("../../Data/stagbeetleblockPool3D-32-ddd-14.raw",14,14,14);	//intensity alpha
		
		//raycaster->loadBlockPool("../../Data/engine-zyx-128X256X256.raw",128,256,256);
		//raycaster->loadBlockPool("../../Data/Testcube-zyx-3X3X3.raw",3,3,3);
		raycaster->loadBlockPool("../../Data/sphere-zyx-200X200X200.raw",200,200,200);
	}
/*
	//visus loading/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	String visus_filename = String("C:/Shusen Liu/Volume Renderer/ViSUS Data/climate/visus.idx");
	VisusFile* vf=VisusOpen((char*)visus_filename.c_str());
	if (!vf) 
		VisusError(__FILE__,__LINE__,"Error: VisusOpen(%s) failed",visus_filename.c_str());

	VisusField* field=&vf->fields[0];

	VisusBox world_box=VisusGetWorldBox(vf,VisusGetMaxH(vf));

	VisusAccess* access=VisusCreateAccess(vf);
	
	VisusQuery* read_query=VisusCreateQuery(vf,world_box,VisusGetMaxH(vf),0,VisusGetMaxH(vf));
		//allocate buffer for binary data
	int slice_data_size=VisusGetBufferSize(vf,field,read_query->totsamples);
	MemoryBlock slice_data;
	bool bOk=slice_data.setSize(slice_data_size,__FILE__,__LINE__);
	VisusReleaseAssert(bOk);
	slice_data.fillWithValue(0);
	//read data from disk, specify the time step
	int time = 10;
	VisusRead(vf,access,read_query,field,vf->time_from+time,VisusCreateData(slice_data_size,slice_data.getData(),0,0));
	
	if (read_query->status!=VISUS_QUERY_FINISHED)
		VisusError(__FILE__,__LINE__,"Error: failed to read data");

	float* Src = (float*)slice_data.getData();
	float fmax = field->Max;
	
	assert(slice_data_size == 4*(world_box.p2.x-world_box.p1.x+1)*(world_box.p2.y-world_box.p1.y+1)*(world_box.p2.z-world_box.p1.z+1));
	unsigned char* buffer= (unsigned char*)malloc(slice_data_size/4);
	for(int i=0; i<slice_data_size/4; i++)
		buffer[i]= (unsigned char)((Src[i]/fmax)*255.0);

	raycaster->loadBlockPool((void*)buffer,world_box.p2.z+1,world_box.p2.y+1,world_box.p2.x+1);
*/
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	transferfunction = new TransferFunction();
	transferfunction->setHistogram(raycaster->getHistogram());

	raycaster->setTransferFunctionTexture(transferfunction->getTexture());
	raycaster->texBlockPool->SetFilterType(GL_NEAREST);
	
	pArcBall = new ArcBall(raycaster->m_Width, raycaster->m_Height);

}

void Save(int control)
{
	transferfunction->SavetoFile(TF_string_list[curr_string]);
}

void Apply(int control)
{
	transferfunction->LoadfromFile(TF_string_list[curr_string]);
}

void Nearest(int control)
{
	raycaster->texBlockPool->SetFilterType(GL_NEAREST);
	raycaster->textureFilterOrder = 0.5;
}
void Linear(int control)
{
	raycaster->texBlockPool->SetFilterType(GL_LINEAR);
	raycaster->textureFilterOrder = 1.5;
}

void Cubic(int control)
{
	raycaster->texBlockPool->SetFilterType(GL_LINEAR);
	raycaster->textureFilterOrder = 2.5;
}

void reloadShader(int control)
{
	raycaster->_createProgram();
}


int main(int argc, char* argv[])
{
	/*unit test for some function*/
	unitTest();

	glutInit(&argc, argv);
	glutInitWindowSize(w, h);

	glutInitDisplayMode(GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGB);

	window_id = glutCreateWindow("GPU RayCaster");

	raycaster = new RayCaster;
	init_gl();
	transferfunction->LoadfromFile("SolidStyle2");

 
	glutDisplayFunc(display); 
    glutReshapeFunc(reshape);
    glutKeyboardFunc(keyboard);
    glutMouseFunc(mouse);
	glutMotionFunc(move);
	
	//GLUI
    glui = GLUI_Master.create_glui( "GLUI", 0, 700, 400); 

	GLUI_Panel *raycaster_panel = new GLUI_Panel( glui, "RayCaster Parameters" );
    GLUI_Spinner *GUIstepsize = new GLUI_Spinner( raycaster_panel, "Stepsize", &stepsize);
	GUIstepsize->set_float_limits( 0.0003f, 0.08f );
    GUIstepsize->set_alignment( GLUI_ALIGN_RIGHT );

    GLUI_Spinner *GUIvar = new GLUI_Spinner( raycaster_panel, "var", &var);
	GUIvar->set_float_limits( 0.00000001f, 0.01f );

	GLUI_Checkbox *checkBox = new GLUI_Checkbox(raycaster_panel,"Show texture packing", &showPacking );
	GLUI_Checkbox *checkBoxTF = new GLUI_Checkbox(raycaster_panel,"Show transferFunction", &showTF );
	GLUI_Checkbox *checkBoxViewMethod = new GLUI_Checkbox(raycaster_panel,"insideView", &viewMethod );

	//integralMethod
	GLUI_Spinner *Integral = new GLUI_Spinner(raycaster_panel,"Use different Integral Method", &integralMethod );
	Integral->set_float_limits( 0.00000001f, 5.0f );

    //GLUI_Spinner *GUIisovalue = new GLUI_Spinner( raycaster_panel, "Isovalue", &isovalue);
	//GUIisovalue->set_float_limits( 0.01f, 1.0f );
    //GUIisovalue->set_alignment( GLUI_ALIGN_RIGHT );

	//List box
	GLUI_Listbox *list = new GLUI_Listbox( glui, "Choose TransforFunction", &curr_string );	 
	for(int i=0; i<4; i++ )
    list->add_item( i, TF_string_list[i] );

   	glui->add_button("Apply Style",0,Apply);
	glui->add_button("Save Style",0,Save);

	//////////texture parameter
	GLUI_Panel *texture_panel = new GLUI_Panel( glui, "Texture Parameters" );
	GLUI_Button *bNN = new GLUI_Button(texture_panel,"Nearest-Linear",0,Nearest);
	GLUI_Button *bl = new GLUI_Button(texture_panel,"Linear",0,Linear);	
	GLUI_Button *cubic = new GLUI_Button(texture_panel,"CubicFast",0,Cubic);

	glui->add_button("reload Shader",0, reloadShader);

	//lighting
    GLUI_Rotation *view_rot = new GLUI_Rotation(glui, "Light Direction", light_rotate );
	view_rot->set_spin( 1.0 );

	glui->set_main_gfx_window( window_id );
    
	GLUI_Master.set_glutIdleFunc( idle ); 
	
	glutMainLoop();

	return 0;
}

